#include "towners.h"

#include "cursor.h"
#include "inv.h"
#include "minitext.h"
#include "stores.h"
#include "utils/language.h"

namespace devilution {
namespace {

std::unique_ptr<byte[]> CowCels;
int CowMsg;
int CowClicks;

/**
 * Maps from direction to coordinate delta, which is used when
 * placing cows in Tristram. A single cow may require space of up
 * to three tiles when being placed on the map.
 */
Point CowOffsets[8] = { { -1, -1 }, { 0, -1 }, { -1, -1 }, { -1, 0 }, { -1, -1 }, { 0, -1 }, { -1, -1 }, { -1, 0 } };

/** Specifies the active sound effect ID for interacting with cows. */
_sfx_id CowPlaying = SFX_NONE;

struct TownerInit {
	_talker_id type;
	Point position;
	direction dir;
	void (*init)(TownerStruct &towner, const TownerInit &initData);
	void (*talk)(PlayerStruct &player, TownerStruct &towner);
};

void NewTownerAnim(TownerStruct &towner, byte *pAnim, uint8_t numFrames, int delay)
{
	towner._tAnimData = pAnim;
	towner._tAnimLen = numFrames;
	towner._tAnimFrame = 1;
	towner._tAnimCnt = 0;
	towner._tAnimDelay = delay;
}

void InitTownerInfo(int i, const TownerInit &initData)
{
	auto &towner = towners[i];

	towner._ttype = initData.type;
	towner.position = initData.position;
	towner.talk = initData.talk;
	towner._tSeed = AdvanceRndSeed();

	dMonster[towner.position.x][towner.position.y] = i + 1;

	initData.init(towner, initData);
}

void InitQstSnds(TownerStruct &towner, _talker_id type)
{
	for (int i = 0; i < MAXQUESTS; i++) {
		towner.qsts[i]._qsttype = quests[i]._qtype;
		towner.qsts[i]._qstmsg = Qtalklist[type][i];
		towner.qsts[i]._qstmsgact = Qtalklist[type][i] != TEXT_NONE;
	}
}

void LoadTownerAnimations(TownerStruct &towner, const char *path, int frames, direction dir, int delay)
{
	towner._tNData = LoadFileInMem(path);
	for (auto &animation : towner._tNAnim) {
		animation = towner._tNData.get();
	}
	NewTownerAnim(towner, towner._tNAnim[dir], frames, delay);
}

/**
 * @brief Load Griswold into the game
 */
void InitSmith(TownerStruct &towner, const TownerInit &initData)
{
	InitQstSnds(towner, initData.type);
	towner._tAnimWidth = 96;
	static const uint8_t AnimOrder[] = {
		// clang-format off
		5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5,
		5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5,
		5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5,
		5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5,
		5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5,
		5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
		1, 1, 1, 1, 1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1, 2, 3, 4
		// clang-format on
	};
	towner.animOrder = AnimOrder;
	towner.animOrderSize = sizeof(AnimOrder);
	LoadTownerAnimations(towner, "Towners\\Smith\\SmithN.CEL", 16, initData.dir, 3);
	towner._tName = _("Griswold the Blacksmith");
}

void InitBarOwner(TownerStruct &towner, const TownerInit &initData)
{
	InitQstSnds(towner, initData.type);
	towner._tAnimWidth = 96;
	static const uint8_t AnimOrder[] = {
		// clang-format off
		1, 2, 3, 3, 2,  1, 16, 15, 14, 14, 15, 16,
		1, 2, 3, 3, 2,  1, 16, 15, 14, 14, 15, 16,
		1, 2, 3, 3, 2,  1, 16, 15, 14, 14, 15, 16,
		1, 2, 3, 3, 2,  1, 16, 15, 14, 14, 15, 16,
		1, 2, 3, 3, 2,  1, 16, 15, 14, 14, 15, 16,
		1, 2, 3, 3, 2,  1, 16, 15, 14, 14, 15, 16,
		1, 2, 3, 3, 2,  1, 16, 15, 14, 14, 15, 16,
		1, 2, 3, 2, 1, 16, 15, 14, 14, 15, 16,
		1, 2, 3, 4, 5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15, 16
		// clang-format on
	};
	towner.animOrder = AnimOrder;
	towner.animOrderSize = sizeof(AnimOrder);
	LoadTownerAnimations(towner, "Towners\\TwnF\\TwnFN.CEL", 16, initData.dir, 3);
	towner._tName = _("Ogden the Tavern owner");
}

void InitTownDead(TownerStruct &towner, const TownerInit &initData)
{
	InitQstSnds(towner, initData.type);
	towner._tAnimWidth = 96;
	towner.animOrder = nullptr;
	towner.animOrderSize = 0;
	LoadTownerAnimations(towner, "Towners\\Butch\\Deadguy.CEL", 8, initData.dir, 6);
	towner._tName = _("Wounded Townsman");
}

void InitWitch(TownerStruct &towner, const TownerInit &initData)
{
	InitQstSnds(towner, initData.type);
	towner._tAnimWidth = 96;
	static const uint8_t AnimOrder[] = {
		// clang-format off
		 4,  4,  4,  5,  6,  6,  6,  5,  4, 15, 14, 13, 13, 13, 14, 15, 4, 5, 6, 6, 6, 5,
		 4,  4,  4,  5,  6,  6,  6,  5,  4, 15, 14, 13, 13, 13, 14, 15, 4, 5, 6, 6, 6, 5,
		 4,  4,  4,  5,  6,  6,  6,  5,  4, 15, 14, 13, 13, 13, 14, 15, 4, 5, 6, 6, 6, 5,
		 4,  3,  2,  1, 19, 18, 19,  1,  2,  1, 19, 18, 19,  1,  2,
		 1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15,
		15, 15, 14, 13, 13, 13, 13, 14, 15,
		15, 15, 14, 13, 12, 12, 12, 11, 10, 10, 10,  9,
		 8,  9, 10, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
		 1,  2,  1, 19, 18, 19,  1,  2,  1,  2,  3
		// clang-format on
	};
	towner.animOrder = AnimOrder;
	towner.animOrderSize = sizeof(AnimOrder);
	LoadTownerAnimations(towner, "Towners\\TownWmn1\\Witch.CEL", 19, initData.dir, 6);
	towner._tName = _("Adria the Witch");
}

void InitBarmaid(TownerStruct &towner, const TownerInit &initData)
{
	InitQstSnds(towner, initData.type);
	towner._tAnimWidth = 96;
	towner.animOrder = nullptr;
	towner.animOrderSize = 0;
	LoadTownerAnimations(towner, "Towners\\TownWmn1\\WmnN.CEL", 18, initData.dir, 6);
	towner._tName = _("Gillian the Barmaid");
}

void InitBoy(TownerStruct &towner, const TownerInit &initData)
{
	InitQstSnds(towner, initData.type);
	towner._tAnimWidth = 96;
	towner.animOrder = nullptr;
	towner.animOrderSize = 0;
	LoadTownerAnimations(towner, "Towners\\TownBoy\\PegKid1.CEL", 20, initData.dir, 6);
	towner._tName = _("Wirt the Peg-legged boy");
}

void InitHealer(TownerStruct &towner, const TownerInit &initData)
{
	InitQstSnds(towner, initData.type);
	towner._tAnimWidth = 96;
	static const uint8_t AnimOrder[] = {
		// clang-format off
		 1,  2,  3,  3,  2,  1, 20, 19, 19, 20,
		 1,  2,  3,  3,  2,  1, 20, 19, 19, 20,
		 1,  2,  3,  3,  2,  1, 20, 19, 19, 20,
		 1,  2,  3,  3,  2,  1, 20, 19, 19, 20,
		 1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15, 16,
		15, 14, 13, 12, 11, 10,  9,  8,  7,  6,  5,  4,
		 5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15, 16,
		15, 14, 13, 12, 11, 10,  9,  8,  7,  6,  5,  4,
		 5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20
		// clang-format on
	};
	towner.animOrder = AnimOrder;
	towner.animOrderSize = sizeof(AnimOrder);
	LoadTownerAnimations(towner, "Towners\\Healer\\Healer.CEL", 20, initData.dir, 6);
	towner._tName = _("Pepin the Healer");
}

void InitTeller(TownerStruct &towner, const TownerInit &initData)
{
	InitQstSnds(towner, initData.type);
	towner._tAnimWidth = 96;
	static const uint8_t AnimOrder[] = {
		// clang-format off
		 1,  1, 25, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15,
		16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 25, 25,  1,  1,  1, 25,
		 1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15,
		14, 13, 12, 11, 10,  9,  8,  7,  6,  5,  4,  3,  2,  1
		// clang-format on
	};
	towner.animOrder = AnimOrder;
	towner.animOrderSize = sizeof(AnimOrder);
	LoadTownerAnimations(towner, "Towners\\Strytell\\Strytell.CEL", 25, initData.dir, 3);
	towner._tName = _("Cain the Elder");
}

void InitDrunk(TownerStruct &towner, const TownerInit &initData)
{
	InitQstSnds(towner, initData.type);
	towner._tAnimWidth = 96;
	static const uint8_t AnimOrder[] = {
		// clang-format off
		 1, 1, 1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 11, 11, 11, 12, 13, 14, 15, 16, 17, 18, 18,
		 1, 1, 1, 18, 17, 16, 15, 14, 13, 12, 11, 10, 11, 12, 13, 14, 15, 16, 17, 18,
		 1, 2, 3,  4,  5,  5,  5,  4,  3,  2
		// clang-format off
	};
	towner.animOrder = AnimOrder;
	towner.animOrderSize = sizeof(AnimOrder);
	LoadTownerAnimations(towner, "Towners\\Drunk\\TwnDrunk.CEL", 18, initData.dir, 3);
	towner._tName = _("Farnham the Drunk");
}

void InitCows(TownerStruct &towner, const TownerInit &initData)
{
	towner._tAnimWidth = 128;
	towner.animOrder = nullptr;
	towner.animOrderSize = 0;
	for (int i = 0; i < 8; i++) {
		towner._tNAnim[i] = CelGetFrameStart(CowCels.get(), i);
	}
	NewTownerAnim(towner, towner._tNAnim[initData.dir], 12, 3);
	towner._tAnimFrame = GenerateRnd(11) + 1;
	towner._tName = _("Cow");

	const Point position = initData.position;
	const Point offset = position + CowOffsets[initData.dir];
	int index = -dMonster[position.x][position.y];
	if (dMonster[position.x][offset.y] == 0)
		dMonster[position.x][offset.y] = index;
	if (dMonster[offset.x][position.y] == 0)
		dMonster[offset.x][position.y] = index;
	if (dMonster[offset.x][offset.y] == 0)
		dMonster[offset.x][offset.y] = index;
}

void InitFarmer(TownerStruct &towner, const TownerInit &initData)
{
	InitQstSnds(towner, initData.type);
	towner._tAnimWidth = 96;
	towner.animOrder = nullptr;
	towner.animOrderSize = 0;
	LoadTownerAnimations(towner, "Towners\\Farmer\\Farmrn2.CEL", 15, initData.dir, 3);
	towner._tName = _("Lester the farmer");
}

void InitCowFarmer(TownerStruct &towner, const TownerInit &initData)
{
	InitQstSnds(towner, initData.type);
	const char *celPath = "Towners\\Farmer\\cfrmrn2.CEL";
	if (quests[Q_JERSEY]._qactive == QUEST_DONE) {
		celPath = "Towners\\Farmer\\mfrmrn2.CEL";
	}
	towner._tAnimWidth = 96;
	towner.animOrder = nullptr;
	towner.animOrderSize = 0;
	LoadTownerAnimations(towner, celPath, 15, initData.dir, 3);
	towner._tName = _("Complete Nut");
}

void InitGirl(TownerStruct &towner, const TownerInit &initData)
{
	InitQstSnds(towner, initData.type);
	const char *celPath = "Towners\\Girl\\Girlw1.CEL";
	if (quests[Q_GIRL]._qactive == QUEST_DONE) {
		celPath = "Towners\\Girl\\Girls1.CEL";
	}
	towner._tAnimWidth = 96;
	towner.animOrder = nullptr;
	towner.animOrderSize = 0;
	LoadTownerAnimations(towner, celPath, 20, initData.dir, 6);
	towner._tName = "Celia";
}

void TownDead(TownerStruct &towner)
{
	if (qtextflag) {
		if (quests[Q_BUTCHER]._qvar1 == 1)
			towner._tAnimCnt = 0; // Freeze while speaking
		return;
	}

	if ((quests[Q_BUTCHER]._qactive == QUEST_DONE || quests[Q_BUTCHER]._qvar1 == 1) && towner._tAnimLen != 1) {
		towner._tAnimLen = 1;
		towner._tName = _("Slain Townsman");
	}
}

void TownerTalk(_speech_id message)
{
	CowClicks = 0;
	CowMsg = 0;
	InitQTextMsg(message);
}

void TalkToBarOwner(PlayerStruct &player, TownerStruct &barOwner)
{
	if (!player._pLvlVisited[0]) {
		InitQTextMsg(TEXT_INTRO);
		return;
	}

	if (quests[Q_SKELKING]._qactive != QUEST_NOTAVAIL) {
		if (player._pLvlVisited[2] || player._pLvlVisited[4]) {
			if (quests[Q_SKELKING]._qvar2 == 0) {
				quests[Q_SKELKING]._qvar2 = 1;
				quests[Q_SKELKING]._qlog = true;
				if (quests[Q_SKELKING]._qactive == QUEST_INIT) {
					quests[Q_SKELKING]._qactive = QUEST_ACTIVE;
					quests[Q_SKELKING]._qvar1 = 1;
				}
				InitQTextMsg(TEXT_KING2);
				NetSendCmdQuest(true, Q_SKELKING);
				return;
			}
			if (quests[Q_SKELKING]._qactive == QUEST_DONE && quests[Q_SKELKING]._qvar2 == 1) {
				quests[Q_SKELKING]._qvar2 = 2;
				quests[Q_SKELKING]._qvar1 = 2;
				InitQTextMsg(TEXT_KING4);
				NetSendCmdQuest(true, Q_SKELKING);
				return;
			}
		}
	}

	if (quests[Q_LTBANNER]._qactive != QUEST_NOTAVAIL) {
		if (player._pLvlVisited[3] && quests[Q_LTBANNER]._qactive != QUEST_DONE) {
			if (quests[Q_LTBANNER]._qvar2 == 0) {
				quests[Q_LTBANNER]._qvar2 = 1;
				if (quests[Q_LTBANNER]._qactive == QUEST_INIT) {
					quests[Q_LTBANNER]._qvar1 = 1;
					quests[Q_LTBANNER]._qactive = QUEST_ACTIVE;
				}
				quests[Q_LTBANNER]._qlog = true;
				InitQTextMsg(TEXT_BANNER2);
				return;
			}
			int i;
			if (quests[Q_LTBANNER]._qvar2 == 1 && player.HasItem(IDI_BANNER, &i)) {
				quests[Q_LTBANNER]._qactive = QUEST_DONE;
				quests[Q_LTBANNER]._qvar1 = 3;
				player.RemoveInvItem(i);
				SpawnUnique(UITEM_HARCREST, barOwner.position.x, barOwner.position.y + 1);
				InitQTextMsg(TEXT_BANNER3);
				return;
			}
		}
	}

	TownerTalk(TEXT_OGDEN1);
	StartStore(STORE_TAVERN);
}

void TalkToDeadguy(PlayerStruct &player, TownerStruct & /*deadguy*/)
{
	if (quests[Q_BUTCHER]._qactive == QUEST_DONE)
		return;

	if (quests[Q_BUTCHER]._qvar1 == 1) {
		player.PlaySpecificSpeach(8);
		return;
	}

	quests[Q_BUTCHER]._qactive = QUEST_ACTIVE;
	quests[Q_BUTCHER]._qlog = true;
	quests[Q_BUTCHER]._qmsg = TEXT_BUTCH9;
	quests[Q_BUTCHER]._qvar1 = 1;
	InitQTextMsg(TEXT_BUTCH9);
	NetSendCmdQuest(true, Q_BUTCHER);
}

void TalkToBlackSmith(PlayerStruct &player, TownerStruct &blackSmith)
{
	if (quests[Q_ROCK]._qactive != QUEST_NOTAVAIL) {
		if (player._pLvlVisited[4] && quests[Q_ROCK]._qactive != QUEST_DONE) {
			if (quests[Q_ROCK]._qvar2 == 0) {
				quests[Q_ROCK]._qvar2 = 1;
				quests[Q_ROCK]._qlog = true;
				if (quests[Q_ROCK]._qactive == QUEST_INIT) {
					quests[Q_ROCK]._qactive = QUEST_ACTIVE;
				}
				InitQTextMsg(TEXT_INFRA5);
				return;
			}
			int i;
			if (quests[Q_ROCK]._qvar2 == 1 && player.HasItem(IDI_ROCK, &i)) {
				quests[Q_ROCK]._qactive = QUEST_DONE;
				player.RemoveInvItem(i);
				SpawnUnique(UITEM_INFRARING, blackSmith.position.x, blackSmith.position.y + 1);
				InitQTextMsg(TEXT_INFRA7);
				return;
			}
		}
	}
	if (quests[Q_ANVIL]._qactive != QUEST_NOTAVAIL) {
		if (player._pLvlVisited[9] && quests[Q_ANVIL]._qactive != QUEST_DONE) {
			if (quests[Q_ANVIL]._qvar2 == 0 && quests[Q_ROCK]._qactive != QUEST_INIT) {
				quests[Q_ANVIL]._qvar2 = 1;
				quests[Q_ANVIL]._qlog = true;
				if (quests[Q_ANVIL]._qactive == QUEST_INIT) {
					quests[Q_ANVIL]._qactive = QUEST_ACTIVE;
				}
				InitQTextMsg(TEXT_ANVIL5);
				return;
			}
			int i;
			if (quests[Q_ANVIL]._qvar2 == 1 && player.HasItem(IDI_ANVIL, &i)) {
				quests[Q_ANVIL]._qactive = QUEST_DONE;
				player.RemoveInvItem(i);
				SpawnUnique(UITEM_GRISWOLD, blackSmith.position.x, blackSmith.position.y + 1);
				InitQTextMsg(TEXT_ANVIL7);
				return;
			}
		}
	}

	TownerTalk(TEXT_GRISWOLD1);
	StartStore(STORE_SMITH);
}

void TalkToWitch(PlayerStruct &player, TownerStruct & /*witch*/)
{
	if (quests[Q_MUSHROOM]._qactive != QUEST_NOTAVAIL) {
		int i;
		if (quests[Q_MUSHROOM]._qactive == QUEST_INIT && player.HasItem(IDI_FUNGALTM, &i)) {
			player.RemoveInvItem(i);
			quests[Q_MUSHROOM]._qactive = QUEST_ACTIVE;
			quests[Q_MUSHROOM]._qlog = true;
			quests[Q_MUSHROOM]._qvar1 = QS_TOMEGIVEN;
			InitQTextMsg(TEXT_MUSH8);
			return;
		}
		if (quests[Q_MUSHROOM]._qactive == QUEST_ACTIVE) {
			if (quests[Q_MUSHROOM]._qvar1 >= QS_TOMEGIVEN && quests[Q_MUSHROOM]._qvar1 < QS_MUSHGIVEN) {
				int i;
				if (player.HasItem(IDI_MUSHROOM, &i)) {
					player.RemoveInvItem(i);
					quests[Q_MUSHROOM]._qvar1 = QS_MUSHGIVEN;
					Qtalklist[TOWN_HEALER][Q_MUSHROOM] = TEXT_MUSH3;
					Qtalklist[TOWN_WITCH][Q_MUSHROOM] = TEXT_NONE;
					quests[Q_MUSHROOM]._qmsg = TEXT_MUSH10;
					InitQTextMsg(TEXT_MUSH10);
					return;
				}
				if (quests[Q_MUSHROOM]._qmsg != TEXT_MUSH9) {
					quests[Q_MUSHROOM]._qmsg = TEXT_MUSH9;
					InitQTextMsg(TEXT_MUSH9);
					return;
				}
			}
			if (quests[Q_MUSHROOM]._qvar1 >= QS_MUSHGIVEN) {
				if (player.HasItem(IDI_BRAIN)) {
					quests[Q_MUSHROOM]._qmsg = TEXT_MUSH11;
					InitQTextMsg(TEXT_MUSH11);
					return;
				}
				if (player.HasItem(IDI_SPECELIX)) {
					InitQTextMsg(TEXT_MUSH12);
					quests[Q_MUSHROOM]._qactive = QUEST_DONE;
					AllItemsList[IDI_SPECELIX].iUsable = true; // TODO modefy item instead of AllItemsList
					return;
				}
			}
		}
	}

	TownerTalk(TEXT_ADRIA1);
	StartStore(STORE_WITCH);
}

void TalkToBarmaid(PlayerStruct &player, TownerStruct & /*barmaid*/)
{
	if (!player._pLvlVisited[21] && player.HasItem(IDI_MAPOFDOOM)) {
		quests[Q_GRAVE]._qactive = QUEST_ACTIVE;
		quests[Q_GRAVE]._qlog = true;
		quests[Q_GRAVE]._qmsg = TEXT_GRAVE8;
		InitQTextMsg(TEXT_GRAVE8);
		return;
	}

	TownerTalk(TEXT_GILLIAN1);
	StartStore(STORE_BARMAID);
}

void TalkToDrunk(PlayerStruct & /*player*/, TownerStruct & /*drunk*/)
{
	TownerTalk(TEXT_FARNHAM1);
	StartStore(STORE_DRUNK);
}

void TalkToHealer(PlayerStruct &player, TownerStruct &healer)
{
	if (quests[Q_PWATER]._qactive != QUEST_NOTAVAIL) {
		if ((player._pLvlVisited[1] || player._pLvlVisited[5]) && quests[Q_PWATER]._qactive == QUEST_INIT) {
			quests[Q_PWATER]._qactive = QUEST_ACTIVE;
			quests[Q_PWATER]._qlog = true;
			quests[Q_PWATER]._qmsg = TEXT_POISON3;
			InitQTextMsg(TEXT_POISON3);
			return;
		}
		if (quests[Q_PWATER]._qactive == QUEST_DONE && quests[Q_PWATER]._qvar1 != 2) {
			quests[Q_PWATER]._qvar1 = 2;
			InitQTextMsg(TEXT_POISON5);
			SpawnUnique(UITEM_TRING, healer.position.x, healer.position.y + 1);
			return;
		}
	}
	int i;
	if (quests[Q_MUSHROOM]._qactive == QUEST_ACTIVE) {
		if (quests[Q_MUSHROOM]._qvar1 >= QS_MUSHGIVEN && quests[Q_MUSHROOM]._qvar1 < QS_BRAINGIVEN && player.HasItem(IDI_BRAIN, &i)) {
			player.RemoveInvItem(i);
			SpawnQuestItem(IDI_SPECELIX, healer.position.x, healer.position.y + 1, 0, 0);
			InitQTextMsg(TEXT_MUSH4);
			quests[Q_MUSHROOM]._qvar1 = QS_BRAINGIVEN;
			Qtalklist[TOWN_HEALER][Q_MUSHROOM] = TEXT_NONE;
			return;
		}
	}

	TownerTalk(TEXT_PEPIN1);
	StartStore(STORE_HEALER);
}

void TalkToBoy(PlayerStruct & /*player*/, TownerStruct & /*boy*/)
{
	TownerTalk(TEXT_WIRT1);
	StartStore(STORE_BOY);
}

void TalkToStoryteller(PlayerStruct &player, TownerStruct & /*storyteller*/)
{
	if (!gbIsMultiplayer) {
		int i;
		if (quests[Q_BETRAYER]._qactive == QUEST_INIT && player.HasItem(IDI_LAZSTAFF, &i)) {
			InitQTextMsg(TEXT_VILE1);
			quests[Q_BETRAYER]._qlog = true;
			quests[Q_BETRAYER]._qactive = QUEST_ACTIVE;
			quests[Q_BETRAYER]._qvar1 = 2;
			player.RemoveInvItem(i);
			return;
		}
	} else {
		if (quests[Q_BETRAYER]._qactive == QUEST_ACTIVE && !quests[Q_BETRAYER]._qlog) {
			InitQTextMsg(TEXT_VILE1);
			quests[Q_BETRAYER]._qlog = true;
			NetSendCmdQuest(true, Q_BETRAYER);
			return;
		}
	}
	if (quests[Q_BETRAYER]._qactive == QUEST_DONE && quests[Q_BETRAYER]._qvar1 == 7) {
		quests[Q_BETRAYER]._qvar1 = 8;
		InitQTextMsg(TEXT_VILE3);
		quests[Q_DIABLO]._qlog = true;
		if (gbIsMultiplayer) {
			NetSendCmdQuest(true, Q_BETRAYER);
			NetSendCmdQuest(true, Q_DIABLO);
		}
		return;
	}

	TownerTalk(TEXT_STORY1);
	StartStore(STORE_STORY);
}

void TalkToCow(PlayerStruct &player, TownerStruct &cow)
{
	if (CowPlaying != SFX_NONE && effect_is_playing(CowPlaying))
		return;

	CowClicks++;

	CowPlaying = TSFX_COW1;
	if (CowClicks == 4) {
		if (gbIsSpawn)
			CowClicks = 0;

		CowPlaying = TSFX_COW2;
	} else if (CowClicks >= 8 && !gbIsSpawn) {
		CowClicks = 4;

		static const int snSfx[3] = { 52, 49, 50 };
		player.PlaySpecificSpeach(snSfx[CowMsg]);
		CowMsg++;
		if (CowMsg >= 3)
			CowMsg = 0;
	}

	PlaySfxLoc(CowPlaying, cow.position.x, cow.position.y);
}

void TalkToFarmer(PlayerStruct &player, TownerStruct &farmer)
{
	switch (quests[Q_FARMER]._qactive) {
	case QUEST_NOTAVAIL:
	case QUEST_INIT:
		if (player.HasItem(IDI_RUNEBOMB)) {
			InitQTextMsg(TEXT_FARMER2);
			quests[Q_FARMER]._qactive = QUEST_ACTIVE;
			quests[Q_FARMER]._qvar1 = 1;
			quests[Q_FARMER]._qmsg = TEXT_FARMER1;
			quests[Q_FARMER]._qlog = true;
			if (gbIsMultiplayer)
				NetSendCmdQuest(true, Q_FARMER);
			break;
		}

		if (!player._pLvlVisited[9] && player._pLevel < 15) {
			_speech_id qt = TEXT_FARMER8;
			if (player._pLvlVisited[2])
				qt = TEXT_FARMER5;
			if (player._pLvlVisited[5])
				qt = TEXT_FARMER7;
			if (player._pLvlVisited[7])
				qt = TEXT_FARMER9;
			InitQTextMsg(qt);
			break;
		}

		InitQTextMsg(TEXT_FARMER1);
		quests[Q_FARMER]._qactive = QUEST_ACTIVE;
		quests[Q_FARMER]._qvar1 = 1;
		quests[Q_FARMER]._qlog = true;
		quests[Q_FARMER]._qmsg = TEXT_FARMER1;
		SpawnRuneBomb(farmer.position.x + 1, farmer.position.y);
		if (gbIsMultiplayer)
			NetSendCmdQuest(true, Q_FARMER);
		break;
	case QUEST_ACTIVE:
		InitQTextMsg(player.HasItem(IDI_RUNEBOMB) ? TEXT_FARMER2 : TEXT_FARMER3);
		break;
	case QUEST_DONE:
		InitQTextMsg(TEXT_FARMER4);
		SpawnRewardItem(IDI_AURIC, farmer.position.x + 1, farmer.position.y);
		quests[Q_FARMER]._qactive = QUEST_HIVE_DONE;
		quests[Q_FARMER]._qlog = false;
		if (gbIsMultiplayer)
			NetSendCmdQuest(true, Q_FARMER);
		break;
	default:
		InitQTextMsg(TEXT_FARMER4);
		break;
	}
}

void TalkToCowFarmer(PlayerStruct &player, TownerStruct &cowFarmer)
{
	int i;
	if (player.HasItem(IDI_GREYSUIT, &i)) {
		InitQTextMsg(TEXT_JERSEY7);
		player.RemoveInvItem(i);
	}

	if (player.HasItem(IDI_BROWNSUIT, &i)) {
		SpawnUnique(UITEM_BOVINE, cowFarmer.position.x + 1, cowFarmer.position.y);
		player.RemoveInvItem(i);
		InitQTextMsg(TEXT_JERSEY8);
		quests[Q_JERSEY]._qactive = QUEST_DONE;
		return;
	}

	if (player.HasItem(IDI_RUNEBOMB)) {
		InitQTextMsg(TEXT_JERSEY5);
		quests[Q_JERSEY]._qactive = QUEST_ACTIVE;
		quests[Q_JERSEY]._qvar1 = 1;
		quests[Q_JERSEY]._qmsg = TEXT_JERSEY4;
		quests[Q_JERSEY]._qlog = true;
		return;
	}

	switch (quests[Q_JERSEY]._qactive) {
	case QUEST_NOTAVAIL:
	case QUEST_INIT:
		InitQTextMsg(TEXT_JERSEY1);
		quests[Q_JERSEY]._qactive = QUEST_HIVE_TEASE1;
		if (gbIsMultiplayer)
			NetSendCmdQuest(true, Q_JERSEY);
		break;
	case QUEST_ACTIVE:
		InitQTextMsg(TEXT_JERSEY5);
		break;
	case QUEST_DONE:
		InitQTextMsg(TEXT_JERSEY1);
		break;
	case QUEST_HIVE_TEASE1:
		InitQTextMsg(TEXT_JERSEY2);
		quests[Q_JERSEY]._qactive = QUEST_HIVE_TEASE2;
		if (gbIsMultiplayer)
			NetSendCmdQuest(true, Q_JERSEY);
		break;
	case QUEST_HIVE_TEASE2:
		InitQTextMsg(TEXT_JERSEY3);
		quests[Q_JERSEY]._qactive = QUEST_HIVE_ACTIVE;
		if (gbIsMultiplayer)
			NetSendCmdQuest(true, Q_JERSEY);
		break;
	case QUEST_HIVE_ACTIVE:
		if (!player._pLvlVisited[9] && player._pLevel < 15) {
			_speech_id qt = TEXT_JERSEY12;
			switch (GenerateRnd(4)) {
			case 0:
				qt = TEXT_JERSEY9;
				break;
			case 1:
				qt = TEXT_JERSEY10;
				break;
			case 2:
				qt = TEXT_JERSEY11;
				break;
			}
			InitQTextMsg(qt);
			break;
		}

		InitQTextMsg(TEXT_JERSEY4);
		quests[Q_JERSEY]._qactive = QUEST_ACTIVE;
		quests[Q_JERSEY]._qvar1 = 1;
		quests[Q_JERSEY]._qmsg = TEXT_JERSEY4;
		quests[Q_JERSEY]._qlog = true;
		SpawnRuneBomb(cowFarmer.position.x + 1, cowFarmer.position.y);
		if (gbIsMultiplayer)
			NetSendCmdQuest(true, Q_JERSEY);
		break;
	default:
		InitQTextMsg(TEXT_JERSEY5);
		break;
	}
}

void TalkToGirl(PlayerStruct &player, TownerStruct &girl)
{
	int i;
	if (player.HasItem(IDI_THEODORE, &i) && quests[Q_GIRL]._qactive != QUEST_DONE) {
		InitQTextMsg(TEXT_GIRL4);
		player.RemoveInvItem(i);
		CreateAmulet(girl.position, 13, false, true);
		quests[Q_GIRL]._qlog = false;
		quests[Q_GIRL]._qactive = QUEST_DONE;
		if (gbIsMultiplayer)
			NetSendCmdQuest(true, Q_GIRL);
	}

	switch (quests[Q_GIRL]._qactive) {
	case QUEST_NOTAVAIL:
	case QUEST_INIT:
		InitQTextMsg(TEXT_GIRL2);
		quests[Q_GIRL]._qactive = QUEST_ACTIVE;
		quests[Q_GIRL]._qvar1 = 1;
		quests[Q_GIRL]._qlog = true;
		quests[Q_GIRL]._qmsg = TEXT_GIRL2;
		if (gbIsMultiplayer)
			NetSendCmdQuest(true, Q_GIRL);
		return;
	case QUEST_ACTIVE:
		InitQTextMsg(TEXT_GIRL3);
		return;
	default:
		PlaySFX(alltext[TEXT_GIRL1].sfxnr);
		return;
	}
}

const TownerInit TownerInitList[] = {
	// clang-format off
	// type         position    dir     init           talk
	{ TOWN_SMITH,   { 62, 63 }, DIR_SW, InitSmith,     TalkToBlackSmith  },
	{ TOWN_HEALER,  { 55, 79 }, DIR_SE, InitHealer,    TalkToHealer      },
	{ TOWN_DEADGUY, { 24, 32 }, DIR_N,  InitTownDead,  TalkToDeadguy     },
	{ TOWN_TAVERN,  { 55, 62 }, DIR_SW, InitBarOwner,  TalkToBarOwner    },
	{ TOWN_STORY,   { 62, 71 }, DIR_S,  InitTeller,    TalkToStoryteller },
	{ TOWN_DRUNK,   { 71, 84 }, DIR_S,  InitDrunk,     TalkToDrunk       },
	{ TOWN_WITCH,   { 80, 20 }, DIR_S,  InitWitch,     TalkToWitch       },
	{ TOWN_BMAID,   { 43, 66 }, DIR_S,  InitBarmaid,   TalkToBarmaid     },
	{ TOWN_PEGBOY,  { 11, 53 }, DIR_S,  InitBoy,       TalkToBoy         },
	{ TOWN_COW,     { 58, 16 }, DIR_SW, InitCows,      TalkToCow         },
	{ TOWN_COW,     { 56, 14 }, DIR_NW, InitCows,      TalkToCow         },
	{ TOWN_COW,     { 59, 20 }, DIR_N,  InitCows,      TalkToCow         },
	{ TOWN_COWFARM, { 61, 22 }, DIR_SW, InitCowFarmer, TalkToCowFarmer   },
	{ TOWN_FARMER,  { 62, 16 }, DIR_S,  InitFarmer,    TalkToFarmer      },
	{ TOWN_GIRL,    { 77, 43 }, DIR_S,  InitGirl,      TalkToGirl        },
	// clang-format on
};

} // namespace

TownerStruct towners[NUM_TOWNERS];

/** Contains the data related to quest gossip for each towner ID. */
_speech_id Qtalklist[NUM_TOWNER_TYPES][MAXQUESTS] = {
	// clang-format off
	//                 Q_ROCK,       Q_MUSHROOM,  Q_GARBUD,  Q_ZHAR,    Q_VEIL,     Q_DIABLO,   Q_BUTCHER,   Q_LTBANNER,   Q_BLIND,     Q_BLOOD,     Q_ANVIL,      Q_WARLORD,    Q_SKELKING,  Q_PWATER,      Q_SCHAMB,   Q_BETRAYER,  Q_GRAVE,     Q_FARMER,  Q_GIRL,    Q_TRADER,  Q_DEFILER, Q_NAKRUL,  Q_CORNSTN, Q_JERSEY
	/*TOWN_SMITH*/   { TEXT_INFRA6,  TEXT_MUSH6,  TEXT_NONE, TEXT_NONE, TEXT_VEIL5, TEXT_NONE,  TEXT_BUTCH5, TEXT_BANNER6, TEXT_BLIND5, TEXT_BLOOD5, TEXT_ANVIL6,  TEXT_WARLRD5, TEXT_KING7,  TEXT_POISON7,  TEXT_BONE5, TEXT_VILE9,  TEXT_GRAVE2, TEXT_NONE, TEXT_NONE, TEXT_NONE, TEXT_NONE, TEXT_NONE, TEXT_NONE, TEXT_NONE },
	/*TOWN_HEALER*/  { TEXT_INFRA3,  TEXT_NONE,   TEXT_NONE, TEXT_NONE, TEXT_VEIL3, TEXT_NONE,  TEXT_BUTCH3, TEXT_BANNER4, TEXT_BLIND3, TEXT_BLOOD3, TEXT_ANVIL3,  TEXT_WARLRD3, TEXT_KING5,  TEXT_POISON4,  TEXT_BONE3, TEXT_VILE7,  TEXT_GRAVE3, TEXT_NONE, TEXT_NONE, TEXT_NONE, TEXT_NONE, TEXT_NONE, TEXT_NONE, TEXT_NONE },
	/*TOWN_DEADGUY*/ { TEXT_NONE,    TEXT_NONE,   TEXT_NONE, TEXT_NONE, TEXT_NONE,  TEXT_NONE,  TEXT_NONE,   TEXT_NONE,    TEXT_NONE,   TEXT_NONE,   TEXT_NONE,    TEXT_NONE,    TEXT_NONE,   TEXT_NONE,     TEXT_NONE,  TEXT_NONE,   TEXT_NONE,   TEXT_NONE, TEXT_NONE, TEXT_NONE, TEXT_NONE, TEXT_NONE, TEXT_NONE, TEXT_NONE },
	/*TOWN_TAVERN*/  { TEXT_INFRA2,  TEXT_MUSH2,  TEXT_NONE, TEXT_NONE, TEXT_VEIL2, TEXT_NONE,  TEXT_BUTCH2, TEXT_NONE,    TEXT_BLIND2, TEXT_BLOOD2, TEXT_ANVIL2,  TEXT_WARLRD2, TEXT_KING3,  TEXT_POISON2,  TEXT_BONE2, TEXT_VILE4,  TEXT_GRAVE5, TEXT_NONE, TEXT_NONE, TEXT_NONE, TEXT_NONE, TEXT_NONE, TEXT_NONE, TEXT_NONE },
	/*TOWN_STORY*/   { TEXT_INFRA1,  TEXT_MUSH1,  TEXT_NONE, TEXT_NONE, TEXT_VEIL1, TEXT_VILE3, TEXT_BUTCH1, TEXT_BANNER1, TEXT_BLIND1, TEXT_BLOOD1, TEXT_ANVIL1,  TEXT_WARLRD1, TEXT_KING1,  TEXT_POISON1,  TEXT_BONE1, TEXT_VILE2,  TEXT_GRAVE6, TEXT_NONE, TEXT_NONE, TEXT_NONE, TEXT_NONE, TEXT_NONE, TEXT_NONE, TEXT_NONE },
	/*TOWN_DRUNK*/   { TEXT_INFRA8,  TEXT_MUSH7,  TEXT_NONE, TEXT_NONE, TEXT_VEIL6, TEXT_NONE,  TEXT_BUTCH6, TEXT_BANNER7, TEXT_BLIND6, TEXT_BLOOD6, TEXT_ANVIL8,  TEXT_WARLRD6, TEXT_KING8,  TEXT_POISON8,  TEXT_BONE6, TEXT_VILE10, TEXT_GRAVE7, TEXT_NONE, TEXT_NONE, TEXT_NONE, TEXT_NONE, TEXT_NONE, TEXT_NONE, TEXT_NONE },
	/*TOWN_WITCH*/   { TEXT_INFRA9,  TEXT_MUSH9,  TEXT_NONE, TEXT_NONE, TEXT_VEIL7, TEXT_NONE,  TEXT_BUTCH7, TEXT_BANNER8, TEXT_BLIND7, TEXT_BLOOD7, TEXT_ANVIL9,  TEXT_WARLRD7, TEXT_KING9,  TEXT_POISON9,  TEXT_BONE7, TEXT_VILE11, TEXT_GRAVE1, TEXT_NONE, TEXT_NONE, TEXT_NONE, TEXT_NONE, TEXT_NONE, TEXT_NONE, TEXT_NONE },
	/*TOWN_BMAID*/   { TEXT_INFRA4,  TEXT_MUSH5,  TEXT_NONE, TEXT_NONE, TEXT_VEIL4, TEXT_NONE,  TEXT_BUTCH4, TEXT_BANNER5, TEXT_BLIND4, TEXT_BLOOD4, TEXT_ANVIL4,  TEXT_WARLRD4, TEXT_KING6,  TEXT_POISON6,  TEXT_BONE4, TEXT_VILE8,  TEXT_GRAVE8, TEXT_NONE, TEXT_NONE, TEXT_NONE, TEXT_NONE, TEXT_NONE, TEXT_NONE, TEXT_NONE },
	/*TOWN_PEGBOY*/  { TEXT_INFRA10, TEXT_MUSH13, TEXT_NONE, TEXT_NONE, TEXT_VEIL8, TEXT_NONE,  TEXT_BUTCH8, TEXT_BANNER9, TEXT_BLIND8, TEXT_BLOOD8, TEXT_ANVIL10, TEXT_WARLRD8, TEXT_KING10, TEXT_POISON10, TEXT_BONE8, TEXT_VILE12, TEXT_GRAVE9, TEXT_NONE, TEXT_NONE, TEXT_NONE, TEXT_NONE, TEXT_NONE, TEXT_NONE, TEXT_NONE },
	/*TOWN_COW*/     { TEXT_NONE,    TEXT_NONE,   TEXT_NONE, TEXT_NONE, TEXT_NONE,  TEXT_NONE,  TEXT_NONE,   TEXT_NONE,    TEXT_NONE,   TEXT_NONE,   TEXT_NONE,    TEXT_NONE,    TEXT_NONE,   TEXT_NONE,     TEXT_NONE,  TEXT_NONE,   TEXT_NONE,   TEXT_NONE, TEXT_NONE, TEXT_NONE, TEXT_NONE, TEXT_NONE, TEXT_NONE, TEXT_NONE },
	/*TOWN_FARMER*/  { TEXT_NONE,    TEXT_NONE,   TEXT_NONE, TEXT_NONE, TEXT_NONE,  TEXT_NONE,  TEXT_NONE,   TEXT_NONE,    TEXT_NONE,   TEXT_NONE,   TEXT_NONE,    TEXT_NONE,    TEXT_NONE,   TEXT_NONE,     TEXT_NONE,  TEXT_NONE,   TEXT_NONE,   TEXT_NONE, TEXT_NONE, TEXT_NONE, TEXT_NONE, TEXT_NONE, TEXT_NONE, TEXT_NONE },
	/*TOWN_GIRL*/    { TEXT_NONE,    TEXT_NONE,   TEXT_NONE, TEXT_NONE, TEXT_NONE,  TEXT_NONE,  TEXT_NONE,   TEXT_NONE,    TEXT_NONE,   TEXT_NONE,   TEXT_NONE,    TEXT_NONE,    TEXT_NONE,   TEXT_NONE,     TEXT_NONE,  TEXT_NONE,   TEXT_NONE,   TEXT_NONE, TEXT_NONE, TEXT_NONE, TEXT_NONE, TEXT_NONE, TEXT_NONE, TEXT_NONE },
	/*TOWN_COWFARM*/ { TEXT_NONE,    TEXT_NONE,   TEXT_NONE, TEXT_NONE, TEXT_NONE,  TEXT_NONE,  TEXT_NONE,   TEXT_NONE,    TEXT_NONE,   TEXT_NONE,   TEXT_NONE,    TEXT_NONE,    TEXT_NONE,   TEXT_NONE,     TEXT_NONE,  TEXT_NONE,   TEXT_NONE,   TEXT_NONE, TEXT_NONE, TEXT_NONE, TEXT_NONE, TEXT_NONE, TEXT_NONE, TEXT_NONE },
	// clang-format on
};

void InitTowners()
{
	assert(CowCels == nullptr);

	CowCels = LoadFileInMem("Towners\\Animals\\Cow.CEL");

	int i = 0;
	for (const auto &townerInit : TownerInitList) {
		switch (townerInit.type) {
		case TOWN_DEADGUY:
			if (quests[Q_BUTCHER]._qactive == QUEST_NOTAVAIL || quests[Q_BUTCHER]._qactive == QUEST_DONE)
				continue;
			break;
		case TOWN_FARMER:
			if (!gbIsHellfire || sgGameInitInfo.bCowQuest)
				continue;
			break;
		case TOWN_COWFARM:
			if (!gbIsHellfire || !sgGameInitInfo.bCowQuest || quests[Q_FARMER]._qactive == 10)
				continue;
			break;
		case TOWN_GIRL:
			if (!gbIsHellfire || !sgGameInitInfo.bTheoQuest || !plr->_pLvlVisited[17])
				continue;
			break;
		default:
			break;
		}

		InitTownerInfo(i, townerInit);
		i++;
	}
}

void FreeTownerGFX()
{
	for (auto &towner : towners) {
		towner._tNData = nullptr;
	}

	CowCels = nullptr;
}

void ProcessTowners()
{
	for (auto &towner : towners) {
		if (towner._ttype == TOWN_DEADGUY) {
			TownDead(towner);
		}

		towner._tAnimCnt++;
		if (towner._tAnimCnt < towner._tAnimDelay) {
			continue;
		}

		towner._tAnimCnt = 0;

		if (towner.animOrderSize > 0) {
			towner._tAnimFrameCnt++;
			if (towner._tAnimFrameCnt > towner.animOrderSize - 1)
				towner._tAnimFrameCnt = 0;

			towner._tAnimFrame = towner.animOrder[towner._tAnimFrameCnt];
			continue;
		}

		towner._tAnimFrame++;
		if (towner._tAnimFrame > towner._tAnimLen)
			towner._tAnimFrame = 1;
	}
}

void TalkToTowner(PlayerStruct &player, int t)
{
	auto &towner = towners[t];

	Point distance = player.position.tile - towner.position;
	if (abs(distance.x) >= 2 || abs(distance.y) >= 2)
		return;

	if (pcurs >= CURSOR_FIRSTITEM) {
		return;
	}

	towner.talk(player, towner);
}

} // namespace devilution
